{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"contentcontainer med left\" style=\"margin-left: -50px;\">\n",
    "<dl class=\"dl-horizontal\">\n",
    "  <dt>Title</dt> <dd> Graph Element</dd>\n",
    "  <dt>Dependencies</dt> <dd>Bokeh</dd>\n",
    "  <dt>Backends</dt> <dd><a href='./Graph.ipynb'>Bokeh</a></dd> <dd><a href='../matplotlib/Graph.ipynb'>Matplotlib</a></dd>\n",
    "</dl>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import holoviews as hv\n",
    "from holoviews import opts\n",
    "hv.extension('bokeh')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``Graph`` element provides an easy way to represent and visualize network graphs. It differs from other elements in HoloViews in that it consists of multiple sub-elements. The data of the ``Graph`` element itself are the abstract edges between the nodes. By default the element will automatically compute concrete ``x`` and ``y`` positions for the nodes and represent them using a ``Nodes`` element, which is stored on the Graph. The abstract edges and concrete node positions are sufficient to render the ``Graph`` by drawing straight-line edges between the nodes. In order to supply explicit edge paths we can also declare ``EdgePaths``, providing explicit coordinates for each edge to follow.\n",
    "\n",
    "To summarize a ``Graph`` consists of three different components:\n",
    "\n",
    "* The ``Graph`` itself holds the abstract edges stored as a table of node indices.\n",
    "* The ``Nodes`` hold the concrete ``x`` and ``y`` positions of each node along with a node ``index``. The ``Nodes`` may also define any number of value dimensions, which can be revealed when hovering over the nodes or to color the nodes by.\n",
    "* The ``EdgePaths`` can optionally be supplied to declare explicit node paths.\n",
    "\n",
    "This reference document describes only basic functionality, for a more detailed summary on how to work with network graphs in HoloViews see the [User Guide](../../../user_guide/Network_Graphs.ipynb).\n",
    "\n",
    "To make the visualizations in this notebook easier to view, the first thing we will do is increase the default width and height:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "opts.defaults(opts.Graph(width=400, height=400))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A simple Graph\n",
    "\n",
    "Let's start by declaring a very simple graph connecting one node to all others. If we simply supply the abstract connectivity of the ``Graph``, it will automatically compute a layout for the nodes using the ``layout_nodes`` operation, which defaults to a circular layout:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Declare abstract edges\n",
    "N = 8\n",
    "node_indices = np.arange(N)\n",
    "source = np.zeros(N)\n",
    "target = node_indices\n",
    "\n",
    "padding = dict(x=(-1.2, 1.2), y=(-1.2, 1.2))\n",
    "\n",
    "simple_graph = hv.Graph(((source, target),)).redim.range(**padding)\n",
    "simple_graph"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Directed graphs\n",
    "\n",
    "The Graph element also allows indicating the directionality of graph edges using arrows. To enable the arrows set ``directed=True`` and to optionally control the ``arrowhead_length`` provide a length as a fraction of the total graph extent:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "simple_graph.opts(directed=True, arrowhead_length=0.05)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Accessing the nodes and edges\n",
    "\n",
    "We can easily access the ``Nodes`` and ``EdgePaths`` on the ``Graph`` element using the corresponding properties:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "simple_graph.nodes + simple_graph.edgepaths"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Additional features\n",
    "\n",
    "Next we will extend this example by supplying explicit edges, node information and edge weights. By constructing the ``Nodes`` explicitly we can declare an additional value dimensions, which are revealed when hovering and/or can be mapped to the color by specifying the ``color_index``. We can also associate additional information with each edge by supplying a value dimension to the ``Graph`` itself, which we can map to a color using the ``edge_color_index``."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Node info\n",
    "np.random.seed(7)\n",
    "x, y = simple_graph.nodes.array([0, 1]).T\n",
    "node_labels = ['Output']+['Input']*(N-1)\n",
    "edge_weights = np.random.rand(8)\n",
    "\n",
    "# Compute edge paths\n",
    "def bezier(start, end, control, steps=np.linspace(0, 1, 100)):\n",
    "    return (1-steps)**2*start + 2*(1-steps)*steps*control+steps**2*end\n",
    "\n",
    "paths = []\n",
    "for node_index in node_indices:\n",
    "    ex, ey = x[node_index], y[node_index]\n",
    "    paths.append(np.column_stack([bezier(x[0], ex, 0), bezier(y[0], ey, 0)]))\n",
    "\n",
    "# Declare Graph\n",
    "nodes = hv.Nodes((x, y, node_indices, node_labels), vdims='Type')\n",
    "graph = hv.Graph(((source, target, edge_weights), nodes, paths), vdims='Weight')\n",
    "\n",
    "graph.redim.range(**padding).opts(node_color='Type', edge_color='Weight',\n",
    "                                  cmap=['blue', 'red'], edge_cmap='viridis')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For full documentation and the available style and plot options, use ``hv.help(hv.Graph).``"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
